﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;

namespace MattDotson.GlobalToolkit
{
    /// <summary>
    /// Extension methods to <see cref="System.Type"/>
    /// </summary>
    public static class TypeExtensionMethods
    {
        private static MethodInfo defaultValueMethod = typeof(TypeExtensionMethods).GetMethod("InternalDefaultValue", BindingFlags.Static | BindingFlags.NonPublic);
        private static Dictionary<Type, Func<object>> delegateCache = new Dictionary<Type, Func<object>>();
        private static ReaderWriterLockSlim rwlock = new ReaderWriterLockSlim();

        /// <summary>
        /// Gets the default value of the <see cref="Type"/> specified by <paramref name="type"/>.
        /// </summary>
        /// <param name="type">The type to get the default value of.</param>
        /// <returns>The default value boxed as an object.</returns>
        public static object DefaultValue(this Type type)
        {
            Func<object> f;

            using (rwlock.UpgradeableReadLock())
            {
                if (!delegateCache.TryGetValue(type, out f))
                {
                    using(rwlock.WriteLock())
                    {
                        MethodInfo mi = defaultValueMethod.MakeGenericMethod(type);
                        f = (Func<object>)Delegate.CreateDelegate(typeof(Func<object>), mi);
                        delegateCache.Add(type, f);
                    }
                }
            }

            return f();
        }

        /// <summary>
        /// Gets the default value for <see cref="Type"/> of <typeparamref name="T"/> by using default(T).  This method is meant to 
        /// support the non-generic lookup of default values.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> for which to get the default value</typeparam>
        /// <returns>The default value of <typeparamref name="T"/></returns>
        private static object InternalDefaultValue<T>()
        {
            return default(T);
        }
    }

    /// <summary>
    /// Extension methods for <see cref="ReaderWriterLockSlim"/>
    /// </summary>
    public static class ReaderWriteLockSlimExtensionMethods
    {
        /// <summary>
        /// Helper struct which will call a method on dispose
        /// </summary>
        struct Disposable : IDisposable
        {
            /// <summary>
            /// The action to be performed on dispose.
            /// </summary>
            readonly Action action;

            /// <summary>
            /// Initializes a new instance of the <see cref="Disposable"/> struct.
            /// </summary>
            /// <param name="action">The action.</param>
            public Disposable(Action action)
            {
                this.action = action;
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
                action();
            }

        }


        #region Lock Helpers

        /// <summary>
        /// Enters a disposable read-lock
        /// </summary>
        /// <param name="lock">The @lock.</param>
        /// <returns></returns>
        public static IDisposable ReadLock(this ReaderWriterLockSlim @lock)
        {
            @lock.EnterReadLock();
            return new Disposable(@lock.ExitReadLock);
        }

        /// <summary>
        /// Enters a disposable upgradable read lock
        /// </summary>
        /// <param name="lock">The @lock.</param>
        /// <returns></returns>
        public static IDisposable UpgradeableReadLock(this ReaderWriterLockSlim @lock)
        {
            @lock.EnterUpgradeableReadLock();
            return new Disposable(@lock.ExitUpgradeableReadLock);
        }

        /// <summary>
        /// Enters a disposable write lock
        /// </summary>
        /// <param name="lock">The @lock.</param>
        /// <returns></returns>
        public static IDisposable WriteLock(this ReaderWriterLockSlim @lock)
        {
            @lock.EnterWriteLock();
            return new Disposable(@lock.ExitWriteLock);
        }

        #endregion
    }  
}
